// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/compositor/reflector_impl.h"

#include "base/bind.h"
#include "base/location.h"
#include "content/browser/compositor/browser_compositor_output_surface.h"
#include "content/browser/compositor/owned_mailbox.h"
#include "ui/compositor/layer.h"

namespace content {

struct ReflectorImpl::LayerData {
    LayerData(ui::Layer* layer)
        : layer(layer)
    {
    }

    ui::Layer* layer;
    bool needs_set_mailbox = false;
};

ReflectorImpl::ReflectorImpl(ui::Compositor* mirrored_compositor,
    ui::Layer* mirroring_layer)
    : mirrored_compositor_(mirrored_compositor)
    , flip_texture_(false)
    , output_surface_(nullptr)
{
    if (mirroring_layer)
        AddMirroringLayer(mirroring_layer);
}

ReflectorImpl::~ReflectorImpl()
{
}

void ReflectorImpl::Shutdown()
{
    if (output_surface_)
        DetachFromOutputSurface();
    // Prevent the ReflectorImpl from picking up a new output surface.
    mirroring_layers_.clear();
}

void ReflectorImpl::DetachFromOutputSurface()
{
    DCHECK(output_surface_);
    output_surface_->SetReflector(nullptr);
    DCHECK(mailbox_.get());
    mailbox_ = nullptr;
    output_surface_ = nullptr;
    for (LayerData* layer_data : mirroring_layers_)
        layer_data->layer->SetShowSolidColorContent();
}

void ReflectorImpl::OnSourceSurfaceReady(
    BrowserCompositorOutputSurface* output_surface)
{
    if (mirroring_layers_.empty())
        return; // Was already Shutdown().
    if (output_surface == output_surface_)
        return; // Is already attached.
    if (output_surface_)
        DetachFromOutputSurface();

    output_surface_ = output_surface;

    flip_texture_ = !output_surface->capabilities().flipped_output_surface;

    output_surface_->SetReflector(this);
}

void ReflectorImpl::OnMirroringCompositorResized()
{
    for (LayerData* layer_data : mirroring_layers_)
        layer_data->layer->SchedulePaint(layer_data->layer->bounds());
}

void ReflectorImpl::AddMirroringLayer(ui::Layer* layer)
{
    DCHECK(layer->GetCompositor());
    DCHECK(mirroring_layers_.end() == FindLayerData(layer));

    LayerData* layer_data = new LayerData(layer);
    if (mailbox_)
        layer_data->needs_set_mailbox = true;
    mirroring_layers_.push_back(layer_data);
    mirrored_compositor_->ScheduleFullRedraw();
}

void ReflectorImpl::RemoveMirroringLayer(ui::Layer* layer)
{
    DCHECK(layer->GetCompositor());

    ScopedVector<LayerData>::iterator iter = FindLayerData(layer);
    DCHECK(iter != mirroring_layers_.end());
    (*iter)->layer->SetShowSolidColorContent();
    mirroring_layers_.erase(iter);

    if (mirroring_layers_.empty() && output_surface_)
        DetachFromOutputSurface();
}

void ReflectorImpl::OnSourceTextureMailboxUpdated(
    scoped_refptr<OwnedMailbox> mailbox)
{
    mailbox_ = mailbox;
    if (mailbox_.get()) {
        for (LayerData* layer_data : mirroring_layers_)
            layer_data->needs_set_mailbox = true;

        // The texture doesn't have the data. Request full redraw on mirrored
        // compositor so that the full content will be copied to mirroring
        // compositor. This full redraw should land us in OnSourceSwapBuffers() to
        // resize the texture appropriately.
        mirrored_compositor_->ScheduleFullRedraw();
    }
}

void ReflectorImpl::OnSourceSwapBuffers(const gfx::Size& surface_size)
{
    if (mirroring_layers_.empty())
        return;

    // Should be attached to the source output surface already.
    DCHECK(mailbox_.get());

    // Request full redraw on mirroring compositor.
    for (LayerData* layer_data : mirroring_layers_)
        UpdateTexture(layer_data, surface_size, layer_data->layer->bounds());
}

void ReflectorImpl::OnSourcePostSubBuffer(const gfx::Rect& swap_rect,
    const gfx::Size& surface_size)
{
    if (mirroring_layers_.empty())
        return;

    // Should be attached to the source output surface already.
    DCHECK(mailbox_.get());

    gfx::Rect mirroring_rect = swap_rect;
    if (flip_texture_) {
        // Flip the coordinates to compositor's one.
        mirroring_rect.set_y(surface_size.height() - swap_rect.y() - swap_rect.height());
    }

    // Request redraw of the dirty portion in mirroring compositor.
    for (LayerData* layer_data : mirroring_layers_)
        UpdateTexture(layer_data, surface_size, mirroring_rect);
}

static void ReleaseMailbox(scoped_refptr<OwnedMailbox> mailbox,
    const gpu::SyncToken& sync_token,
    bool is_lost)
{
    mailbox->UpdateSyncToken(sync_token);
}

ScopedVector<ReflectorImpl::LayerData>::iterator ReflectorImpl::FindLayerData(
    ui::Layer* layer)
{
    return std::find_if(mirroring_layers_.begin(), mirroring_layers_.end(),
        [layer](const LayerData* layer_data) {
            return layer_data->layer == layer;
        });
}

void ReflectorImpl::UpdateTexture(ReflectorImpl::LayerData* layer_data,
    const gfx::Size& source_size,
    const gfx::Rect& redraw_rect)
{
    if (layer_data->needs_set_mailbox) {
        layer_data->layer->SetTextureMailbox(
            cc::TextureMailbox(mailbox_->holder()),
            cc::SingleReleaseCallback::Create(base::Bind(ReleaseMailbox, mailbox_)),
            source_size);
        layer_data->needs_set_mailbox = false;
    } else {
        layer_data->layer->SetTextureSize(source_size);
    }
    layer_data->layer->SetBounds(gfx::Rect(source_size));
    layer_data->layer->SetTextureFlipped(flip_texture_);
    layer_data->layer->SchedulePaint(redraw_rect);
}

} // namespace content
